#include "bfont.h"
#include <vector>
#include <fstream>
#include <iostream>
#include "vortex/core/asset.h"
#include "vortex/core/logging.h"

namespace vortex
{
    BFont::BFont()
    {

    }

    BFont::~BFont()
    {
        cleanUp();
    }

    void BFont::MapVertex(const std::string& text,
        std::vector<glm::vec3>& vertices,
        std::vector<glm::vec2>& uvs,
        std::vector<uint16_t>& indices, float scale)
    {
        uint16_t lastIndex = 0;
        float offsetX = 0, offsetY = 0;
        for (auto c : text)
        {
            // each char represented as a quad.
            const auto glyphInfo = makeGlyphInfo(c, offsetX, offsetY);
            offsetX = glyphInfo.offsetX;
            offsetY = glyphInfo.offsetY;

            vertices.emplace_back(glyphInfo.positions[0] * scale);
            vertices.emplace_back(glyphInfo.positions[1] * scale);
            vertices.emplace_back(glyphInfo.positions[2] * scale);
            vertices.emplace_back(glyphInfo.positions[3] * scale);
            uvs.emplace_back(glyphInfo.uvs[0]);
            uvs.emplace_back(glyphInfo.uvs[1]);
            uvs.emplace_back(glyphInfo.uvs[2]);
            uvs.emplace_back(glyphInfo.uvs[3]);
            indices.push_back(lastIndex);
            indices.push_back(lastIndex + 1);
            indices.push_back(lastIndex + 2);
            indices.push_back(lastIndex);
            indices.push_back(lastIndex + 2);
            indices.push_back(lastIndex + 3);

            lastIndex += 4;
        }
    }

    GlyphInfo BFont::makeGlyphInfo(uint32_t character, float offsetX, float offsetY) const
    {
        stbtt_aligned_quad quad;

        stbtt_GetPackedQuad(font_.charInfo.get(), font_.atlasWidth, font_.atlasHeight,
            character - font_.firstChar, &offsetX, &offsetY, &quad, 1);
        const auto xmin = quad.x0;
        const auto xmax = quad.x1;
        const auto ymin = -quad.y1;
        const auto ymax = -quad.y0;

        GlyphInfo info{};
        info.offsetX = offsetX;
        info.offsetY = offsetY;
        info.positions[0] = { xmin, ymin, 0 };
        info.positions[1] = { xmin, ymax, 0 };
        info.positions[2] = { xmax, ymax, 0 };
        info.positions[3] = { xmax, ymin, 0 };
        info.uvs[0] = { quad.s0, quad.t1 };
        info.uvs[1] = { quad.s0, quad.t0 };
        info.uvs[2] = { quad.s1, quad.t0 };
        info.uvs[3] = { quad.s1, quad.t1 };

        return info;
    }

    void BFont::Init(const std::string& fontfile)
    {
        // clean previously created texture.
        cleanUp();

        auto fontData = readFile(get_asset_path("fonts/OpenSans-Regular.ttf"));
        auto atlasData = std::make_unique<uint8_t[]>(font_.atlasWidth * font_.atlasHeight);

        font_.charInfo = std::make_unique<stbtt_packedchar[]>(font_.charCount);

        stbtt_pack_context context;
        if (!stbtt_PackBegin(&context, atlasData.get(), font_.atlasWidth, font_.atlasHeight, 0, 1, nullptr))
            panic("Failed to initialize font");

        stbtt_PackSetOversampling(&context, font_.oversampleX, font_.oversampleY);
        if (!stbtt_PackFontRange(&context, fontData.data(), 0, float(font_.size), font_.firstChar, font_.charCount, font_.charInfo.get()))
            panic("Failed to pack font");

        stbtt_PackEnd(&context);

        glGenTextures(1, &font_.texture);
        glBindTexture(GL_TEXTURE_2D, font_.texture);
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, font_.atlasWidth, font_.atlasHeight, 0, GL_RED, GL_UNSIGNED_BYTE, atlasData.get());
        glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
        glGenerateMipmap(GL_TEXTURE_2D);
        Unbind();
    }

    void BFont::Bind() const
    {
        glBindTexture(GL_TEXTURE_2D, font_.texture);
        // glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        // glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, font_.atlasWidth, font_.atlasHeight, 0, GL_RED, GL_UNSIGNED_BYTE, atlasData.get());
        // glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
        // glGenerateMipmap(GL_TEXTURE_2D);
    }

    void BFont::Unbind() const
    {
        glBindTexture(GL_TEXTURE_2D, 0);
    }
    
    std::vector<uint8_t> BFont::readFile(const std::string& path)
    {
        std::ifstream file(path, std::ios::binary | std::ios::ate);
        if (!file.is_open())
        {
            std::cout << "Failed to open file " << path << std::endl;
            return std::vector<uint8_t>();
        }
        
        const auto size = file.tellg();
        file.seekg(0, std::ios::beg);
        auto bytes = std::vector<uint8_t>(size);
        file.read(reinterpret_cast<char*>(&bytes[0]), size);
        file.close();

        return bytes;
    }

    void BFont::cleanUp()
    {
        if (font_.texture != 0)
            glDeleteTextures(1, &font_.texture);
    }
}



